package com.orion.config.security;

import com.orion.config.token.JwtProperties;
import com.orion.domain.SysUser;
import com.orion.entity.Result;
import com.orion.enumeration.TokenCodeEnum;
import com.orion.util.JwtUtil;
import com.orion.util.RedisUtil;
import com.orion.util.ResponseUtil;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @author Administrator
 * @date 2021/11/11
 */
@Component
public class AccessTokenWebFilter implements WebFilter {

    @Resource(name = "jwtProperties")
    private JwtProperties jwtProperties;

    @Value("${security.white-paths}")
    private String[] whitePaths;

    private List<Pattern> patterns = new ArrayList<>();

    @PostConstruct
    public void setPatterns() {
        if (whitePaths.length > 0) {
            for (String whitePath : whitePaths) {
                Pattern pattern = Pattern.compile(whitePath);
                this.patterns.add(pattern);
            }
        }
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        System.out.println("in AccessTokenWebFilter....");
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        if (needValidToken(serverHttpRequest)) {
            String token = serverHttpRequest.getHeaders().getFirst("a-token");
            if (StringUtils.isNotBlank(token)) {
                Claims claims = JwtUtil.parseToken(token, jwtProperties.getAccessSalt());
                System.out.println("claims in AccessTokenWebFilter : " + claims);
                Integer id = claims.get("id", Integer.class);
                String username = claims.get("username", String.class);
                List<String> authorities = claims.get("authorities", List.class);
                String accessKey = String.join(":", id.toString(), username, "access");

                Boolean exists = RedisUtil.exists(accessKey);
                if (!exists) {
                    Result r = TokenCodeEnum.TOKEN_EXPIRED.toResult();
                    return ResponseUtil.jsonResp(exchange.getResponse(), r);
                }
                /*Mono<SecurityContext> single = ReactiveSecurityContextHolder.getContext()
                        .filter(ctx -> ctx.getAuthentication() == null || !ctx.getAuthentication().isAuthenticated())
                        .subscriberContext(
                                ReactiveSecurityContextHolder.withAuthentication(
                                        createAuthentication(id, username, authorities)
                                )
                        ).;*/

                return chain.filter(exchange).subscriberContext(
                        ReactiveSecurityContextHolder.withAuthentication(
                                createAuthentication(id, username, authorities)
                        )
                );
            }
            Result r = TokenCodeEnum.TOKEN_NOT_EXIST.toResult();
            return ResponseUtil.jsonResp(exchange.getResponse(), r);
        }
        return chain.filter(exchange);
    }

    private Authentication createAuthentication(Integer id, String username, List<String> authorities) {
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>(authorities.size());
        for (String authority : authorities) {
            grantedAuthorities.add(new SimpleGrantedAuthority(authority));
        }
        SysUser temp = new SysUser(id, username, "", grantedAuthorities);
        return new UsernamePasswordAuthenticationToken(temp, null, grantedAuthorities);
    }

    private boolean needValidToken(ServerHttpRequest serverHttpRequest) {
        boolean sign = true;
        if (!patterns.isEmpty()) {
            String path = serverHttpRequest.getURI().getRawPath();
            for (Pattern pattern : patterns) {
                boolean isMatch = pattern.matcher(path).matches();
                if (isMatch) {
                    sign = false;
                    break;
                }
            }
        }
        return sign;
    }
}
